/**
 * 
 */
package org.mobicents.tests.diameter;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.jdiameter.api.Answer;
import org.jdiameter.api.ApplicationId;
import org.jdiameter.api.Avp;
import org.jdiameter.api.AvpDataException;
import org.jdiameter.api.AvpSet;
import org.jdiameter.api.Configuration;
import org.jdiameter.api.EventListener;
import org.jdiameter.api.IllegalDiameterStateException;
import org.jdiameter.api.InternalException;
import org.jdiameter.api.Message;
import org.jdiameter.api.MetaData;
import org.jdiameter.api.Network;
import org.jdiameter.api.NetworkReqListener;
import org.jdiameter.api.Request;
import org.jdiameter.api.SessionFactory;
import org.jdiameter.api.Stack;
import org.jdiameter.api.StackType;
import org.jdiameter.client.impl.parser.MessageImpl;
import org.jdiameter.client.impl.parser.MessageParser;
import org.jdiameter.server.impl.StackImpl;
import org.jdiameter.server.impl.helpers.XMLConfiguration;
import org.mobicents.diameter.dictionary.AvpDictionary;
import org.mobicents.diameter.dictionary.AvpRepresentation;

/**
 * Base class for all soak/cps tests
 * 
 * @author baranowb
 * 
 */
public abstract class AbstractStackRunner implements NetworkReqListener, EventListener<Request, Answer> {

	protected final Logger log = Logger.getLogger(getClass());
	//protected static String clientHost = "uac.mobicents.org";
	//protected static String clientPort = "13868";
	//protected static String clientURI = "aaa://" + clientHost + ":" + clientPort;

	// protected static String serverHost = "127.0.0.1";
	protected static String serverHost = "uas.mobicents.org";
	protected static String serverPort = "3868";
	protected static String serverURI = "aaa://" + serverHost + ":" + serverPort;

	protected static String realmName = "mobicents.org";

	protected final int[] prefilledAVPs = new int[] { Avp.DESTINATION_HOST, Avp.DESTINATION_REALM, Avp.ORIGIN_HOST, Avp.ORIGIN_REALM, Avp.SESSION_ID,
			Avp.VENDOR_SPECIFIC_APPLICATION_ID };

	protected MessageParser parser = new MessageParser();
	protected Stack stack;
	protected SessionFactory factory;
	protected InputStream configFile;
	protected AvpDictionary dictionary = AvpDictionary.INSTANCE;
	private boolean run = true;

	/**
	 * 
	 */
	public AbstractStackRunner() {
		// TODO Auto-generated constructor stub
	}

	public void configure(InputStream f) throws Exception {
		this.configFile = f;
		// add more
		try {
			dictionary.parseDictionary(this.getClass().getClassLoader().getResourceAsStream("dictionary.xml"));
			log.info("AVP Dictionary successfully parsed.");
		} catch (Exception e) {
			e.printStackTrace();
		}
		initStack();
	}

	private void initStack() {
		if (log.isInfoEnabled()) {
			log.info("Initializing Stack...");
		}

		try {

			this.stack = new StackImpl();
		} catch (Exception e) {
			e.printStackTrace();
			stack.destroy();
			return;
		}

		try {
			InputStream is;
			if (configFile != null) {
				is = this.configFile;
			} else {
				String configFile = "jdiameter-config.xml";
				is = this.getClass().getClassLoader().getResourceAsStream(configFile);
			}
			Configuration config = new XMLConfiguration(is);
			factory = stack.init(config);
			if (log.isInfoEnabled()) {
				log.info("Stack Configuration successfully loaded.");
			}
			Network network = stack.unwrap(Network.class);

			Set<org.jdiameter.api.ApplicationId> appIds = stack.getMetaData().getLocalPeer().getCommonApplications();

			log.info("Diameter Stack  :: Supporting " + appIds.size() + " applications.");

			// network.addNetworkReqListener(this,
			// ApplicationId.createByAccAppId( 193, 19302 ));

			for (org.jdiameter.api.ApplicationId appId : appIds) {
				log.info("Diameter Stack Mux :: Adding Listener for [" + appId + "].");
				network.addNetworkReqListener(this, appId);
			}

		} catch (Exception e) {
			e.printStackTrace();
			stack.destroy();
			return;
		}

		MetaData metaData = stack.getMetaData();
		if (metaData.getStackType() != StackType.TYPE_SERVER || metaData.getMinorVersion() <= 0) {
			stack.destroy();
			if (log.isEnabledFor(org.apache.log4j.Level.ERROR)) {
				log.error("Incorrect driver");
			}
			return;
		}

		try {
			if (log.isInfoEnabled()) {
				log.info("Starting stack");
			}
			stack.start();
			if (log.isInfoEnabled()) {
				log.info("Stack is running.");
			}
		} catch (Exception e) {
			e.printStackTrace();
			stack.destroy();
			return;
		}
		if (log.isInfoEnabled()) {
			log.info("Stack initialization successfully completed.");
		}
	}

	/**
	 * @return the run
	 */
	public boolean isRun() {
		return run;
	}

	/**
	 * @param run the run to set
	 */
	public void setRun(boolean run) {
		this.run = run;
	}

	public void performTestRun() {

		log.info("Press 'q' to stop execution");
		while (run) {
			try {
				Thread.sleep(1000);

				if (System.in.available() > 0) {

					int r = System.in.read();
					if (r == 'q') {
						run = false;
						break;
					}
				}
			} catch (Exception e) {
				e.printStackTrace();

			}
		}

		clean();
	}

	protected void clean() {

		if (stack != null) {
			try {
				stack.stop(10, TimeUnit.SECONDS);

				stack = null;
				factory = null;
			} catch (IllegalDiameterStateException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InternalException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	protected void dumpMessage(Message message, boolean sending) {
		if (log.isInfoEnabled()) {
			log.info((sending?"Sending ":"Received ") + (message.isRequest() ? "Request: " : "Answer: ") + message.getCommandCode() + "\nE2E:"
					+ message.getEndToEndIdentifier() + "\nHBH:" + message.getHopByHopIdentifier() + "\nAppID:" + message.getApplicationId());
			log.info("Request AVPS: \n");
			try {
				printAvps(message.getAvps());
			} catch (AvpDataException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	protected Message createAnswer(Request request, int answerCode, ApplicationId appId) throws InternalException, IllegalDiameterStateException {

		int commandCode = 0;
		long endToEndId = 0;
		long hopByHopId = 0;
		commandCode = request.getCommandCode();
		endToEndId = request.getEndToEndIdentifier();
		hopByHopId = request.getHopByHopIdentifier();

		Message raw = stack.getSessionFactory().getNewRawSession().createMessage(commandCode, appId, hopByHopId, endToEndId);
		AvpSet avps = raw.getAvps();

		// inser session iD
		avps.insertAvp(0, 263, request.getSessionId(), false);
		// add result //asUnsignedInt32
		avps.addAvp(268, 2001l, true);
		// origin host
		avps.addAvp(264, serverHost, true);
		// origin realm
		avps.addAvp(296, realmName, true);
		raw.setProxiable(true);
		raw.setRequest(false);
		// ((MessageImpl)raw).setPeer(((MessageImpl)request).getPeer());
		return raw;

	}

	protected void printAvps(AvpSet avpSet) throws AvpDataException {
		printAvpsAux(avpSet, 0);
	}

	/**
	 * Prints the AVPs present in an AvpSet with a specified 'tab' level
	 * 
	 * @param avpSet
	 *            the AvpSet containing the AVPs to be printed
	 * @param level
	 *            an int representing the number of 'tabs' to make a pretty
	 *            print
	 * @throws AvpDataException
	 */
	private void printAvpsAux(AvpSet avpSet, int level) throws AvpDataException {
		String prefix = "                      ".substring(0, level * 2);

		for (Avp avp : avpSet) {
			AvpRepresentation avpRep = AvpDictionary.INSTANCE.getAvp(avp.getCode(), avp.getVendorId());

			if (avpRep != null && avpRep.getType().equals("Grouped")) {
				log.info(prefix + "<avp name=\"" + avpRep.getName() + "\" code=\"" + avp.getCode() + "\" vendor=\"" + avp.getVendorId() + "\">");
				printAvpsAux(avp.getGrouped(), level + 1);
				log.info(prefix + "</avp>");
			} else if (avpRep != null) {
				String value = "";

				if (avpRep.getType().equals("Integer32"))
					value = String.valueOf(avp.getInteger32());
				else if (avpRep.getType().equals("Integer64") || avpRep.getType().equals("Unsigned64"))
					value = String.valueOf(avp.getInteger64());
				else if (avpRep.getType().equals("Unsigned32"))
					value = String.valueOf(avp.getUnsigned32());
				else if (avpRep.getType().equals("Float32"))
					value = String.valueOf(avp.getFloat32());
				else
					value = avp.getOctetString();

				log.info(prefix + "<avp name=\"" + avpRep.getName() + "\" code=\"" + avp.getCode() + "\" vendor=\"" + avp.getVendorId()
						+ "\" value=\"" + value + "\" />");
			}
		}
	}

}
